package org.coone.netproxy.server;

import org.coone.netproxy.config.NetProxyConfig;
import org.coone.netproxy.constants.NetProxyConstants;
import org.coone.netproxy.log.Logger;
import org.coone.netproxy.log.LoggerFactory;
import org.coone.netproxy.message.Type;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.locks.ReentrantLock;

/**
 *  网络代理服务
 */
public class NetProxyServer implements Server {
	private static Logger logger = LoggerFactory
			.getLogger(NetProxyServer.class);

	private final static ReentrantLock lock = new ReentrantLock();

	private static NetProxyConfig netProxyConfig;

	private ServerSocket serverSocket;

	public NetProxyServer() {
		new NetProxyServer(NetProxyConstants.PROXY_CONFIG_DEFAULT);
	}

	public NetProxyServer(String configFile) {
		netProxyConfig = NetProxyConfig.getInstance();
		netProxyConfig.loadProperties(configFile);
	}

	/**
	 *  网络代理服务线程
	 */
	class NetProxyServiceThread extends Thread {
		private Socket frontSocket;
		private Socket backendSocket;
		private InputStream frontIn;
		private OutputStream frontOut;
		private InputStream backendIn;
		private OutputStream backendOut;
		boolean runFlag = true;

		public NetProxyServiceThread(Socket frontSocket, Socket backendSocket) {
			if (null == frontSocket || null == backendSocket) {
				runFlag = false;
				return;
			}

			this.frontSocket = frontSocket;
			this.backendSocket = backendSocket;

			try {
				this.frontSocket.setKeepAlive(true);

				backendIn = backendSocket.getInputStream();
				backendOut = backendSocket.getOutputStream();
				frontIn = frontSocket.getInputStream();
				frontOut = frontSocket.getOutputStream();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		@Override
		public void run() {
			try {
				while (runFlag) {
//					frontSocket.sendUrgentData(0xFF);

					byte[] clientCache = new byte[frontIn.available()];
					frontIn.read(clientCache);

					if (clientCache.length > 0) {
						logger.info(clientCache, Type.REQUEST);
					}

					backendOut.write(clientCache);
					backendOut.flush();

					byte[] serverCache = new byte[backendIn.available()];
					backendIn.read(serverCache);
					if (serverCache.length > 0) {
						logger.info(serverCache, Type.RESPONSE);
					}

					frontOut.write(serverCache);
					frontOut.flush();
				}
			} catch (IOException e) {
				logger.error("DBProxy server has an exception at running!", e);
			} finally {
				logger.info("close the Server socket and the io.");
				try {
					frontSocket.close();
					backendSocket.close();
				} catch (IOException e) {
					logger.error("DBProxy server close the socket has error!",
							e);
				}
			}
		}
	}

	private boolean checkPortConfig(int listenPort) {
		if (listenPort < 0 || listenPort > 65536) {
			throw new IllegalArgumentException("Invalid start port: "
					+ listenPort);
		}
		ServerSocket ss = null;
		DatagramSocket ds = null;
		try {
			ss = new ServerSocket(listenPort);
			ss.setReuseAddress(true);
			ds = new DatagramSocket(listenPort);
			ds.setReuseAddress(true);
			return true;
		} catch (IOException e) {
		} finally {
			if (ds != null) {
				ds.close();
			}
			if (ss != null) {
				try {
					ss.close();
				} catch (IOException e) {
				}
			}
		}
		return false;
	}

	public void start() {
		int listenPort = netProxyConfig.getInt(
				NetProxyConstants.PROXY_SERVER_LISTEN_PORT,
				NetProxyConstants.PROXY_SERVER_LISTEN_DEFAULT_PORT);

		if (!checkPortConfig(listenPort)) {
			throw new IllegalStateException("port: " + listenPort
					+ " already in use!");
		}

		try {
			serverSocket = new ServerSocket(listenPort);
			logger.info("The Server is start: " + serverSocket);

			while (true) {
				Socket frontSocket = serverSocket.accept();
				Socket backendSocket = new Socket(
						netProxyConfig
								.getProperty(NetProxyConstants.BACKEND_DATABASE_SERVER_IP),
						netProxyConfig
								.getInt(NetProxyConstants.BACKEND_DATABASE_SERVER_PORT,
										NetProxyConstants.PROXY_SERVER_LISTEN_DEFAULT_PORT));

				NetProxyServiceThread dbProxyServiceThread = new NetProxyServiceThread(
						frontSocket, backendSocket);
				dbProxyServiceThread.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void stop() {
		System.exit(0);
	}
}
